#include <ximera/net/address.hpp>
#include <ximera/common/utils.hpp>
#include <algorithm>

#if defined(XIMERA_WIN32) || defined(XIMERA_POCKETPC)
#include <winsock2.h>
#endif

#include <sstream>

namespace ximera { namespace net
{
    IPAddress IPAddress::any;

    IPAddress::IPAddress()
        : _addressV4(INADDR_ANY),
          _version(IPVersion::IPv4)
    {
    }

    IPAddress::IPAddress(String const& ip)
        : _addressV4(0),
          _version(IPVersion::Unknown)
    {
        if (std::count(ip.begin(), ip.end(), L'.') == 3)
            _version = IPVersion::IPv4;

        parse(ip);
    }

    IPAddress::IPAddress(String const& ip, IPVersion version)
        : _addressV4(0),
          _version(version)
    {
        parse(ip);
    }

    String IPAddress::toString() const
    {
        String result;

        if (_version == IPVersion::IPv4)
        {
            UInt32 nl = htonl(_addressV4);
            for (char* p = inet_ntoa(*reinterpret_cast<in_addr const*>(&nl)); *p; ++p)
                result.push_back(*p);
        }

        return result;
    }

    UInt32 IPAddress::toInt() const
    {
        return _addressV4;
    }

    IPVersion IPAddress::version() const
    {
        return _version;
    }

    bool IPAddress::operator==(IPAddress const& rhs) const
    {
        return _addressV4 == rhs._addressV4;
    }

    void IPAddress::parse(String const& ip)
    {
        switch (_version)
        {
        case IPVersion::IPv4:
            _addressV4 = ntohl(inet_addr(string_util::wstr2str(ip).c_str()));
            break;

        case IPVersion::IPv6:
            // TODO
            break;
        }
    }

    // -----------------------------------------

    IPEndPoint::IPEndPoint()
        : _address(),
          _port(0)
    {
    }

    IPEndPoint::IPEndPoint(IPAddress const& address, Port port)
        : _address(address),
          _port(port)
    {
    }

    IPEndPoint::IPEndPoint(IPAddress const& address, String const& service)
        : _address(address),
          _port(getPort(service))
    {
    }

    IPEndPoint::IPEndPoint(String const& host, Port port)
        : _address(getAddress(host)),
          _port(port)
    {
    }

    IPEndPoint::IPEndPoint(String const& host, String const& service)
        : _address(getAddress(host)),
          _port(getPort(service))
    {
    }

    IPAddress const& IPEndPoint::address() const
    {
        return _address;
    }

    void IPEndPoint::address(IPAddress const& value)
    {
        _address = value;
    }

    Port const& IPEndPoint::port() const
    {
        return _port;
    }

    void IPEndPoint::port(Port const& value)
    {
        _port = value;
    }

    bool IPEndPoint::operator==(IPEndPoint const& rhs) const
    {
        return _address == rhs._address && _port == rhs._port;
    }

    Port IPEndPoint::getPort(String const& service) const
    {
    #if defined(XIMERA_WIN32)
        servent* serv = getservbyname(string_util::wstr2str(service).c_str(), "tcp");
        return (serv ? ntohs(serv->s_port) : 0);
    #elif defined(XIMERA_XBOX)
        return 0;
    #else
        return 0;
    #endif
    }

    IPAddress IPEndPoint::getAddress(String const& host) const
    {
    #if defined(XIMERA_WIN32)
        hostent* hostEnt = gethostbyname(string_util::wstr2str(host).c_str());

        if (hostEnt)
        {
            std::wstringstream strm;
            strm << (unsigned int) (unsigned char) hostEnt->h_addr_list[0][0] << "." <<
                (unsigned int) (unsigned char) hostEnt->h_addr_list[0][1] << "." <<
                (unsigned int) (unsigned char) hostEnt->h_addr_list[0][2] << "." <<
                (unsigned int) (unsigned char) hostEnt->h_addr_list[0][3];

            return IPAddress(strm.str(), IPVersion::IPv4);
        }

        return IPAddress();

    #elif defined(XIMERA_XBOX)
        return IPAddress();
    #else
        return IPAddress();
    #endif
    }
}}